#define DEBUG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.TextEditor.Undo;

namespace ICSharpCode.TextEditor.Document
{
	internal sealed class DefaultDocument : IDocument
	{
		private bool readOnly = false;

		private LineManager lineTrackingStrategy;

		private BookmarkManager bookmarkManager;

		private ITextBufferStrategy textBufferStrategy;

		private IFormattingStrategy formattingStrategy;

		private FoldingManager foldingManager;

		private UndoStack undoStack = new UndoStack();

		private ITextEditorProperties textEditorProperties = new DefaultTextEditorProperties();

		private MarkerStrategy markerStrategy;

		private List<TextAreaUpdate> updateQueue = new List<TextAreaUpdate>();

		public LineManager LineManager
		{
			get
			{
				return this.lineTrackingStrategy;
			}
			set
			{
				this.lineTrackingStrategy = value;
			}
		}

		public MarkerStrategy MarkerStrategy
		{
			get
			{
				return this.markerStrategy;
			}
			set
			{
				this.markerStrategy = value;
			}
		}

		public ITextEditorProperties TextEditorProperties
		{
			get
			{
				return this.textEditorProperties;
			}
			set
			{
				this.textEditorProperties = value;
			}
		}

		public UndoStack UndoStack => this.undoStack;

		public IList<LineSegment> LineSegmentCollection => this.lineTrackingStrategy.LineSegmentCollection;

		public bool ReadOnly
		{
			get
			{
				return this.readOnly;
			}
			set
			{
				this.readOnly = value;
			}
		}

		public ITextBufferStrategy TextBufferStrategy
		{
			get
			{
				return this.textBufferStrategy;
			}
			set
			{
				this.textBufferStrategy = value;
			}
		}

		public IFormattingStrategy FormattingStrategy
		{
			get
			{
				return this.formattingStrategy;
			}
			set
			{
				this.formattingStrategy = value;
			}
		}

		public FoldingManager FoldingManager
		{
			get
			{
				return this.foldingManager;
			}
			set
			{
				this.foldingManager = value;
			}
		}

		public IHighlightingStrategy HighlightingStrategy
		{
			get
			{
				return this.lineTrackingStrategy.HighlightingStrategy;
			}
			set
			{
				this.lineTrackingStrategy.HighlightingStrategy = value;
			}
		}

		public int TextLength => this.textBufferStrategy.Length;

		public BookmarkManager BookmarkManager
		{
			get
			{
				return this.bookmarkManager;
			}
			set
			{
				this.bookmarkManager = value;
			}
		}

		public string TextContent
		{
			get
			{
				return this.GetText(0, this.textBufferStrategy.Length);
			}
			set
			{
				Debug.Assert(this.textBufferStrategy != null);
				Debug.Assert(this.lineTrackingStrategy != null);
				this.OnDocumentAboutToBeChanged(new DocumentEventArgs(this, 0, 0, value));
				this.textBufferStrategy.SetContent(value);
				this.lineTrackingStrategy.SetContent(value);
				this.undoStack.ClearAll();
				this.OnDocumentChanged(new DocumentEventArgs(this, 0, 0, value));
				this.OnTextContentChanged(EventArgs.Empty);
			}
		}

		public int TotalNumberOfLines => this.lineTrackingStrategy.TotalNumberOfLines;

		public List<TextAreaUpdate> UpdateQueue => this.updateQueue;

		public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged
		{
			add
			{
				this.lineTrackingStrategy.LineLengthChanged += value;
			}
			remove
			{
				this.lineTrackingStrategy.LineLengthChanged -= value;
			}
		}

		public event EventHandler<LineCountChangeEventArgs> LineCountChanged
		{
			add
			{
				this.lineTrackingStrategy.LineCountChanged += value;
			}
			remove
			{
				this.lineTrackingStrategy.LineCountChanged -= value;
			}
		}

		public event EventHandler<LineEventArgs> LineDeleted
		{
			add
			{
				this.lineTrackingStrategy.LineDeleted += value;
			}
			remove
			{
				this.lineTrackingStrategy.LineDeleted -= value;
			}
		}

		public event DocumentEventHandler DocumentAboutToBeChanged;

		public event DocumentEventHandler DocumentChanged;

		public event EventHandler UpdateCommited;

		public event EventHandler TextContentChanged;

		public void Insert(int offset, string text)
		{
			if (!this.readOnly)
			{
				this.OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, -1, text));
				this.textBufferStrategy.Insert(offset, text);
				this.lineTrackingStrategy.Insert(offset, text);
				this.undoStack.Push(new UndoableInsert(this, offset, text));
				this.OnDocumentChanged(new DocumentEventArgs(this, offset, -1, text));
			}
		}

		public void Remove(int offset, int length)
		{
			if (!this.readOnly)
			{
				this.OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length));
				this.undoStack.Push(new UndoableDelete(this, offset, this.GetText(offset, length)));
				this.textBufferStrategy.Remove(offset, length);
				this.lineTrackingStrategy.Remove(offset, length);
				this.OnDocumentChanged(new DocumentEventArgs(this, offset, length));
			}
		}

		public void Replace(int offset, int length, string text)
		{
			if (!this.readOnly)
			{
				this.OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length, text));
				this.undoStack.Push(new UndoableReplace(this, offset, this.GetText(offset, length), text));
				this.textBufferStrategy.Replace(offset, length, text);
				this.lineTrackingStrategy.Replace(offset, length, text);
				this.OnDocumentChanged(new DocumentEventArgs(this, offset, length, text));
			}
		}

		public char GetCharAt(int offset)
		{
			return this.textBufferStrategy.GetCharAt(offset);
		}

		public string GetText(int offset, int length)
		{
			if (length < 0)
			{
				throw new ArgumentOutOfRangeException("length", length, "length < 0");
			}
			return this.textBufferStrategy.GetText(offset, length);
		}

		public string GetText(ISegment segment)
		{
			return this.GetText(segment.Offset, segment.Length);
		}

		public int GetLineNumberForOffset(int offset)
		{
			return this.lineTrackingStrategy.GetLineNumberForOffset(offset);
		}

		public LineSegment GetLineSegmentForOffset(int offset)
		{
			return this.lineTrackingStrategy.GetLineSegmentForOffset(offset);
		}

		public LineSegment GetLineSegment(int line)
		{
			return this.lineTrackingStrategy.GetLineSegment(line);
		}

		public int GetFirstLogicalLine(int lineNumber)
		{
			return this.lineTrackingStrategy.GetFirstLogicalLine(lineNumber);
		}

		public int GetLastLogicalLine(int lineNumber)
		{
			return this.lineTrackingStrategy.GetLastLogicalLine(lineNumber);
		}

		public int GetVisibleLine(int lineNumber)
		{
			return this.lineTrackingStrategy.GetVisibleLine(lineNumber);
		}

		public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
		{
			return this.lineTrackingStrategy.GetNextVisibleLineAbove(lineNumber, lineCount);
		}

		public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
		{
			return this.lineTrackingStrategy.GetNextVisibleLineBelow(lineNumber, lineCount);
		}

		public TextLocation OffsetToPosition(int offset)
		{
			int lineNumberForOffset = this.GetLineNumberForOffset(offset);
			LineSegment lineSegment = this.GetLineSegment(lineNumberForOffset);
			return new TextLocation(offset - lineSegment.Offset, lineNumberForOffset);
		}

		public int PositionToOffset(TextLocation p)
		{
			if (p.Y >= this.TotalNumberOfLines)
			{
				return 0;
			}
			LineSegment lineSegment = this.GetLineSegment(p.Y);
			return Math.Min(this.TextLength, lineSegment.Offset + Math.Min(lineSegment.Length, p.X));
		}

		public void UpdateSegmentListOnDocumentChange<T>(List<T> list, DocumentEventArgs e) where T : ISegment
		{
			int num = ((e.Length > 0) ? e.Length : 0);
			int num2 = ((e.Text != null) ? e.Text.Length : 0);
			for (int i = 0; i < list.Count; i++)
			{
				ISegment segment = list[i];
				int num3 = segment.Offset;
				int num4 = segment.Offset + segment.Length;
				if (e.Offset <= num3)
				{
					num3 -= num;
					if (num3 < e.Offset)
					{
						num3 = e.Offset;
					}
				}
				if (e.Offset < num4)
				{
					num4 -= num;
					if (num4 < e.Offset)
					{
						num4 = e.Offset;
					}
				}
				Debug.Assert(num3 <= num4);
				if (num3 == num4)
				{
					list.RemoveAt(i);
					i--;
					continue;
				}
				if (e.Offset <= num3)
				{
					num3 += num2;
				}
				if (e.Offset < num4)
				{
					num4 += num2;
				}
				Debug.Assert(num3 < num4);
				segment.Offset = num3;
				segment.Length = num4 - num3;
			}
		}

		private void OnDocumentAboutToBeChanged(DocumentEventArgs e)
		{
			if (this.DocumentAboutToBeChanged != null)
			{
				this.DocumentAboutToBeChanged(this, e);
			}
		}

		private void OnDocumentChanged(DocumentEventArgs e)
		{
			if (this.DocumentChanged != null)
			{
				this.DocumentChanged(this, e);
			}
		}

		public void RequestUpdate(TextAreaUpdate update)
		{
			if (this.updateQueue.Count != 1 || this.updateQueue[0].TextAreaUpdateType != 0)
			{
				if (update.TextAreaUpdateType == TextAreaUpdateType.WholeTextArea)
				{
					this.updateQueue.Clear();
				}
				this.updateQueue.Add(update);
			}
		}

		public void CommitUpdate()
		{
			if (this.UpdateCommited != null)
			{
				this.UpdateCommited(this, EventArgs.Empty);
			}
		}

		private void OnTextContentChanged(EventArgs e)
		{
			if (this.TextContentChanged != null)
			{
				this.TextContentChanged(this, e);
			}
		}

		[Conditional("DEBUG")]
		internal static void ValidatePosition(IDocument document, TextLocation position)
		{
			document.GetLineSegment(position.Line);
		}
	}
}
